Repository navigation
Enforce PEC boundaries and the GDS PEC mask after external field excitation - #166
Merged
ajnonaka merged 3 commits intoNov 11, 2026
Merged
Conversation
…olveE`/`EvolveB` have already applied the field boundary conditions and the PEC mask. A hard (or soft) source whose flag region touches a PEC domain boundary, or a PEC layer from a GDS/numpy file, therefore overwrites them. For example, it can leave a nonzero tangential E on a PEC wall or along a metal trace. This was the original motivation for AMReX-Microelectronics#20. That PR is now stale (~1900 commits behind), and rebasing it as written would reintroduce soft-source double counting, because it moved the excitation inside the per-level/per-patch evolve routines. This PR fixes the issue without moving the excitation calls. At the end of `ApplyExternalFieldExcitationOnGrid(int externalfieldtype, DtType)`, the routine now re-enforces PEC on whatever was just excited: - **E** (`AllExternal`, `EfieldExternal`, `EfieldExternalPML`): - `ApplyEfieldBoundary(lev, PatchType::fine)`, i.e., domain PEC, including the split E fields in the PML - `ApplyPECMaskToEfield(lev, include_guard_cells=true)`, i.e., the GDS/numpy PEC mask on the grid (valid + guard cells, since the excitation also writes guard cells) and, if extended, in the PML - **B** (`AllExternal`, `BfieldExternal`): `PEC::ApplyPECtoBfield` only. The full `ApplyBfieldBoundary` is intentionally not called, because its Silver-Mueller update is not idempotent and must not run twice in a half step. Each step only runs when it applies: domain PEC when `PEC::isAnyBoundaryPEC()`, the mask when `use_PEC_mask` is on with the macroscopic solver, and only for fields whose excitation parser is active. Because the fix lives inside the excitation routine, it covers every call site: the t=0 `AllExternal` source, both B half-steps, E, E in the PML (`ApplyExcitationInPML`), and the LLG call in `SyncCurrentAndRho`. The existing `DtType` soft-source scaling is unchanged. New `WarpX::ApplyPECMaskToEfield(lev, include_guard_cells)`: - grid: `E *= PEC_fp` on the fine level, optionally including guard cells - PML (with `extend_PEC_mask_into_pml`): mask both split components; for the CPML, also reset the ψ_E memory variables on masked edges - no-op unless `use_PEC_mask` is on and `em_solver_medium == Macroscopic`. The mask is only initialized by `MacroscopicProperties`, so the old unconditional multiply in `OneStep` would have used an uninitialized mask with the vacuum solver. The two inline mask blocks (grid, then PML + CPML ψ) are replaced by a single `ApplyPECMaskToEfield(lev, false)` call after the PML update. It is still applied *before* `FillBoundaryE` / `PML::Exchange`, so guard cells and the PML interface plane see masked E. The PML E update does not read grid E, so applying the grid mask after the PML update instead of before is equivalent. The mask block after the E excitation is removed. It is now covered by `MacroscopicEvolveE` (no excitation) and by `ApplyExternalFieldExcitationOnGrid` (after excitation). The old block also only masked valid cells and did not touch the PML. `InitializePECFromSigma` only sets the valid region, so `PEC_fp` guard cells stayed 1. A `FillBoundary` on `PEC_fp[x,y,z]` now fills interior and periodic guard cells from the neighboring grids, so the mask can be applied to E guard cells. Guard cells outside the domain stay 1; the PML has its own mask. | Case | Before | After | |---|---|---| | Source touching a PEC domain boundary | excitation overwrites PEC | PEC enforced | | Source overlapping the GDS mask on the grid | masked (valid cells only); guard cells stale | masked, valid + guard cells | | Source overlapping the GDS mask in the PML (`ApplyExcitationInPML=1`) | excitation overwrites mask | masked; CPML ψ reset | | t=0 hard source overlapping PEC / mask | unmasked for the first B half-step | masked | - H / H_bias (LLG): there is no PEC boundary condition or mask for H, so a hard H source on a PEC wall or inside metal is not corrected. - B is not masked by the GDS PEC mask. - PML guard cells after a PML excitation: only valid PML cells are masked; guard cells are refreshed by the `FillBoundaryE` that follows `DampPML`. - Silver-Mueller boundaries are not re-applied after excitation, so a source on a Silver-Mueller boundary still overrides it for that step. - [x] Syntax check (`-fsyntax-only`, 3D, AMReX 25.07) with and without `WARPX_MAG_LLG` - [ ] Full build (GNUmake / CMake), with and without `USE_LLG` - [ ] GDS case with no excitation: results unchanged vs. `development` (up to roundoff) - [ ] Hard-source plane through a metal trace: tangential E on masked edges is exactly zero on the grid - [ ] Same with `ApplyExcitationInPML=1`, split-field PML and CPML: masked edges zero in the PML, ψ_E zero there - [ ] Hard-source plane intersecting a PEC domain boundary: tangential E on the wall stays zero - [ ] Multi-box run: masked E consistent across box boundaries (guard cells) - [ ] Existing regression tests unchanged Supersedes AMReX-Microelectronics#20.
…benchmark will need to be changed
ajnonaka
marked this pull request as ready for review
November 11, 2026 00:06
ajnonaka
merged commit Nov 11, 2026
8a2400f
into
AMReX-Microelectronics:development
15 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
External field excitations are applied in
OneStep_nosubafterEvolveE/EvolveBhave already applied the field boundary conditions and the PEC mask. A hard or soft source whose flag region touches a PEC domain boundary, or a PEC layer from a GDS/numpy file, therefore overwrites them. For example, it can leave a nonzero tangential E on a PEC wall or along a metal trace.With this PR, PEC always wins: after any excitation, the domain PEC boundary condition and the PEC mask are re-applied, regardless of the source type.
This was the original motivation for #20. That PR is now stale (~1900 commits behind), and rebasing it as written would reintroduce soft-source double counting, because it moved the excitation inside the per-level/per-patch evolve routines. This PR fixes the issue without moving the excitation calls.
Behavior change
Inputs that drive a port by placing a hard source on a PEC wall (tangential E on the wall) relied on the old ordering, where the excitation overwrote PEC. With this PR those sources are zeroed by PEC. Such ports must be placed one cell inside the domain, on the first interior node.
Examples/Tests/circuits/inputs_regression_roger_pecis updated accordingly (see below). Other inputs that use the same pattern (e.g., theroger_pec*family) should be checked.Changes
Re-apply PEC after excitation (
WarpXExternalEMFields.cpp)At the end of
ApplyExternalFieldExcitationOnGrid(int externalfieldtype, DtType):AllExternal,EfieldExternal,EfieldExternalPML):ApplyEfieldBoundary(lev, PatchType::fine), i.e., domain PEC, including the split E fields in the PMLApplyPECMaskToEfield(lev, include_guard_cells=true), i.e., the GDS/numpy PEC mask on the grid (valid + guard cells, since the excitation also writes guard cells) and, if extended, in the PMLAllExternal,BfieldExternal):PEC::ApplyPECtoBfieldonly. The fullApplyBfieldBoundaryis intentionally not called, because its Silver-Mueller update is not idempotent and must not run twice in a half step.Each step only runs when it applies: domain PEC when
PEC::isAnyBoundaryPEC(), the mask whenuse_PEC_maskis on with the macroscopic solver, and only for fields whose excitation parser is active.Because the fix lives inside the excitation routine, it covers every call site: the t=0
AllExternalsource, both B half-steps, E, E in the PML (ApplyExcitationInPML), and the LLG call inSyncCurrentAndRho. The existingDtTypesoft-source scaling is unchanged.Consolidate the PEC mask (
WarpX.H,WarpXFieldBoundaries.cpp)New
WarpX::ApplyPECMaskToEfield(lev, include_guard_cells):E *= PEC_fpon the fine level, optionally including guard cellsextend_PEC_mask_into_pml): mask both split components; for the CPML, also reset the ψ_E memory variables on masked edgesuse_PEC_maskis on andem_solver_medium == Macroscopic. The mask is only initialized byMacroscopicProperties, so the old unconditional multiply inOneStepwould have used an uninitialized mask with the vacuum solver.Use the helper in
MacroscopicEvolveE(WarpXPushFieldsEM.cpp)The two inline mask blocks (grid, then PML + CPML ψ) are replaced by a single
ApplyPECMaskToEfield(lev, false)call after the PML update. It is still applied beforeFillBoundaryE/PML::Exchange, so guard cells and the PML interface plane see masked E. The PML E update does not read grid E, so applying the grid mask after the PML update instead of before is equivalent.Remove the redundant mask in
OneStep_nosub(WarpXEvolve.cpp)The mask block after the E excitation is removed. It is now covered by
MacroscopicEvolveE(no excitation) and byApplyExternalFieldExcitationOnGrid(after excitation). The old block also only masked valid cells and did not touch the PML.Fill mask guard cells at initialization (
MacroscopicProperties.cpp)InitializePECFromSigmaonly sets the valid region, soPEC_fpguard cells stayed 1. AFillBoundaryonPEC_fp[x,y,z]now fills interior and periodic guard cells from the neighboring grids, so the mask can be applied to E guard cells. Guard cells outside the domain stay 1; the PML has its own mask.Regression input (
Examples/Tests/circuits/inputs_regression_roger_pec)Gaps fixed
ApplyExcitationInPML=1)Not changed
FillBoundaryEthat followsDampPML.inputs_regression_roger_pec: in the Nb layer under the center line, the metal and port flag terms overlap and sum to 2, so those cells act as a soft source. This predates this PR.Testing
-fsyntax-only, 3D, AMReX 25.07) with and withoutWARPX_MAG_LLGUSE_LLGinputs_regression_roger_pec: nonzero fields with the port moved inward; benchmark regeneratedroger_pec*inputs checked for ports on PEC wallsdevelopment(up to roundoff)ApplyExcitationInPML=1, split-field PML and CPML: masked edges zero in the PML, ψ_E zero thereSupersedes #20.